package com.example.Util;

import android.annotation.SuppressLint;
import android.content.Context;

import java.util.Arrays;


public class DevComm {

    public static final int CMD_PREFIX_CODE = (0xAA55);//命令包
    public static final int RCM_PREFIX_CODE = (0x55AA);//响应包
    public static final int CMD_DATA_PREFIX_CODE = (0xA55A);//指令数据包
    public static final int RCM_DATA_PREFIX_CODE = (0x5AA5);//响应数据包
    public static final int SZ_IMG_WIDTH = (256);
    public static final int SZ_IMG_HEIGHT = (288);
    // Command
    public static final int CMD_VERIFY_CODE = (0x0101);
    public static final int CMD_IDENTIFY_CODE = (0x0102);
    public static final int CMD_ENROLL_CODE = (0x0103);
    public static final int CMD_ENROLL_ONETIME_CODE = (0x0104);
    public static final int CMD_CLEAR_TEMPLATE_CODE = (0x0105);
    public static final int CMD_CLEAR_ALLTEMPLATE_CODE = (0x0106);
    public static final int CMD_FINGER_DETECT_CODE = (0x0113);
    public static final int CMD_FEATURE_OF_CAPTURED_FP_CODE = (0x011A);

    public static final int CMD_IDENTIFY_FREE_CODE = (0x0125);
    public static final int CMD_UP_IMAGE_CODE = (0x012C);
    public static final int CMD_CHANGE_TEMPLATE_CODE = (0x0129);
    public static final int CMD_WRITE_TEMPLATE_CODE = (0x010B);
    public static final int CMD_FP_CANCEL_CODE = (0x0130);

    public static final int CMD_TEST_CONNECTION_CODE = (0x0150);

    public static final int DATA_SPLIT_UNIT = (498);
    public static final int OLD_USER_TEMPLATE_SIZE = (498);
    // Error Code
    public static final int ERR_SUCCESS = (0);
    public static final int ERR_FAIL = (1);
    /*public static final int ERR_CONTINUE = (2);*/
    public static final int ERR_COMM_FAIL = (3);
    public static final int ERR_VERIFY = (0x11);
    public static final int ERR_IDENTIFY = (0x12);
    public static final int ERR_TMPL_EMPTY = (0x13);
    public static final int ERR_TMPL_NOT_EMPTY = (0x14);
    public static final int ERR_ALL_TMPL_EMPTY = (0x15);
    public static final int ERR_EMPTY_ID_NOEXIST = (0x16);
    public static final int ERR_BROKEN_ID_NOEXIST = (0x17);
    public static final int ERR_INVALID_TMPL_DATA = (0x18);
    public static final int ERR_DUPLICATION_ID = (0x19);
    public static final int ERR_TOO_FAST = (0x20);
    public static final int ERR_BAD_QUALITY = (0x21);
    public static final int ERR_SMALL_LINES = (0x22);
    public static final int ERR_TIME_OUT = (0x23);
    public static final int ERR_NOT_AUTHORIZED = (0x24);
    public static final int ERR_GENERALIZE = (0x30);

    public static final int ERR_FP_CANCEL = (0x41);
    public static final int ERR_INTERNAL = (0x50);
    public static final int ERR_MEMORY = (0x51);
    public static final int ERR_EXCEPTION = (0x52);
    public static final int ERR_INVALID_TMPL_NO = (0x60);
    public static final int ERR_INVALID_PARAM = (0x70);
    public static final int ERR_NO_RELEASE = (0x71);
    public static final int ERR_INVALID_OPERATION_MODE = (0x72);
    public static final int ERR_NOT_SET_PWD = (0x74);
    public static final int ERR_FP_NOT_DETECTED = (0x75);
    public static final int ERR_ADJUST_SENSOR = (0x76);

    // Return Value
    public static final int NEED_FIRST_SWEEP = (0xFFF1);
    public static final int NEED_SECOND_SWEEP = (0xFFF2);
    public static final int NEED_THIRD_SWEEP = (0xFFF3);
    public static final int NEED_RELEASE_FINGER = (0xFFF4);
    public static final int DETECT_FINGER = (0x01);
    public static final int NO_DETECT_FINGER = (0x00);
    public static final int ONCE_UP_IMAGE_UINT = (60000);

    // Packet
    public static final int MAX_DATA_LEN = (610);
    public static final int CMD_PACKET_LEN = (22);

    ;

    // Template
    public static final int MAX_RECORD_COUNT = (5000);
    public static final int SZ_MAX_RECORD_SIZE = (900);
    public static final int SZ_TEMPLATE_SIZE = (570);
    public static final int SCSI_TIMEOUT = (3000);
    public static final int SZ_MAX_FP_TIME_OUT = (60);
    public static final int COMM_SLEEP_TIME = (100);

    public static final int COMM_TIMEOUT = (15000);

    public int m_nPacketSize;

    public byte[] abyPacket = new byte[64 * 1024];
    public byte[] m_abyPacket2 = new byte[MAX_DATA_LEN + 10];
    public byte[] m_abyPacketTmp = new byte[64 * 1024];

    public static final int VID = 0x2009;
    public static final int PID = 0x7638;

    private static boolean sendPacketThreadWork = false;

    // USB
    private final UsbController usbController;

    public DevComm() {

        // USB Init
        usbController = new UsbController();

    }


    @SuppressLint("SuspiciousIndentation")
    public boolean openComm(Context context) {
        if (context == null) {
            return false;
        }
        if (usbController.init(context)) {
            return testConnection() == (short) ERR_SUCCESS;
        } else {
            return false;
        }
    }

    public boolean closeComm() {
        return usbController.unInit();
    }

    public int testConnection() {
        boolean w_bRet;

        initPacket((short) CMD_TEST_CONNECTION_CODE, true);
        addCheckSum(true);

        w_bRet = sendCommand((short) CMD_TEST_CONNECTION_CODE);

        if (!w_bRet) {
            return ERR_COMM_FAIL;
        }

        if (getRetCode() != ERR_SUCCESS) {
            return ERR_FAIL;
        }

        return ERR_SUCCESS;
    }

    public short getRetCode() {
        return (short) ((int) ((abyPacket[7] << 8) & 0x0000FF00) | (int) (abyPacket[6] & 0x000000FF));
    }

    public short getDataLen() {
        return (short) (((abyPacket[5] << 8) & 0x0000FF00) | (abyPacket[4] & 0x000000FF));
    }

    public void setDataLen(short p_wDataLen) {
        abyPacket[4] = (byte) (p_wDataLen & 0xFF);
        abyPacket[5] = (byte) (((p_wDataLen & 0xFF00) >> 8) & 0xFF);
    }

    public short getCmdData(boolean p_bFirst) {
        if (p_bFirst) {
            return (short) (((abyPacket[7] << 8) & 0x0000FF00) | (abyPacket[6] & 0x000000FF));
        } else {
            return (short) (((abyPacket[9] << 8) & 0x0000FF00) | (abyPacket[8] & 0x000000FF));
        }
    }

    public void setDataLen2(short p_wDataLen) {
        m_abyPacket2[4] = (byte) (p_wDataLen & 0xFF);
        m_abyPacket2[5] = (byte) (((p_wDataLen & 0xFF00) >> 8) & 0xFF);
    }

    public void setCmdData(short p_wData, boolean p_bFirst) {
        if (p_bFirst) {
            abyPacket[6] = (byte) (p_wData & 0xFF);
            abyPacket[7] = (byte) (((p_wData & 0xFF00) >> 8) & 0xFF);
        } else {
            abyPacket[8] = (byte) (p_wData & 0xFF);
            abyPacket[9] = (byte) (((p_wData & 0xFF00) >> 8) & 0xFF);
        }
    }

    private short getDataPacketLen() {
        return (short) (((abyPacket[5] << 8) & 0x0000FF00) | (abyPacket[4] & 0x000000FF) + 6);
    }

    //包识别码+CMD（功能码）
    //(CODE & 0xFF 获取16进制的后两位并转换为byte类型)
    //((CMD_DATA_PREFIX_CODE & 0xFF00) >> 8) & 0xFF 获取16进制的前两位并转换为byte类型)
    void initPacket(short p_wCmd, boolean p_bCmdData) {
        memset(abyPacket, (byte) 0, CMD_PACKET_LEN);

        if (p_bCmdData) {
            abyPacket[0] = (byte) (CMD_PREFIX_CODE & 0xFF);//55
            abyPacket[1] = (byte) (((CMD_PREFIX_CODE & 0xFF00) >> 8) & 0xFF);//AA
        } else {
            abyPacket[0] = (byte) (CMD_DATA_PREFIX_CODE & 0xFF);
            abyPacket[1] = (byte) (((CMD_DATA_PREFIX_CODE & 0xFF00) >> 8) & 0xFF);
        }

        abyPacket[2] = (byte) (p_wCmd & 0xFF);
        abyPacket[3] = (byte) (((p_wCmd & 0xFF00) >> 8) & 0xFF);
    }

    void initPacket2(short p_wCmd, boolean p_bCmdData) {
        memset(m_abyPacket2, (byte) 0, CMD_PACKET_LEN);

        //g_pPacketBuffer->wPrefix = p_bCmdData?CMD_PREFIX_CODE:CMD_DATA_PREFIX_CODE;
        if (p_bCmdData) {
            m_abyPacket2[0] = (byte) (CMD_PREFIX_CODE & 0xFF);
            m_abyPacket2[1] = (byte) (((CMD_PREFIX_CODE & 0xFF00) >> 8) & 0xFF);
        } else {
            m_abyPacket2[0] = (byte) (CMD_DATA_PREFIX_CODE & 0xFF);
            m_abyPacket2[1] = (byte) (((CMD_DATA_PREFIX_CODE & 0xFF00) >> 8) & 0xFF);
        }

        //g_pPacketBuffer->wCMD_RCM = p_wCMD;
        m_abyPacket2[2] = (byte) (p_wCmd & 0xFF);
        m_abyPacket2[3] = (byte) (((p_wCmd & 0xFF00) >> 8) & 0xFF);
    }

    short getCheckSum(boolean p_bCmdData) {
        short w_wRet;
        short w_nI;

        w_wRet = 0;
        if (p_bCmdData) {
            for (w_nI = 0; w_nI < CMD_PACKET_LEN; w_nI++)
                w_wRet += (abyPacket[w_nI] & 0xFF);
        } else {
            for (w_nI = 0; w_nI < getDataPacketLen(); w_nI++)
                w_wRet += (abyPacket[w_nI] & 0xFF);
        }
        return w_wRet;
    }

    short addCheckSum(boolean p_bCmdData) {
        short w_wRet = 0;
        short w_wLen;
        int w_nI;

        if (p_bCmdData) {
            w_wLen = CMD_PACKET_LEN;
        } else {
            w_wLen = getDataPacketLen();
        }

        for (w_nI = 0; w_nI < w_wLen; w_nI++){
            w_wRet += (abyPacket[w_nI] & 0xFF);
        }

        abyPacket[w_wLen] = (byte) (w_wRet & 0xFF);
        abyPacket[w_wLen + 1] = (byte) (((w_wRet & 0xFF00) >> 8) & 0xFF);

        return w_wRet;
    }

    short addCheckSum2(boolean cmdData) {
        short w_wRet = 0;
        short w_wLen = 0;
        int w_nI;

        if (cmdData)
            w_wLen = CMD_PACKET_LEN;
        else
            w_wLen = getDataPacketLen();

        for (w_nI = 0; w_nI < w_wLen; w_nI++)
            w_wRet += (m_abyPacket2[w_nI] & 0xFF);

        m_abyPacket2[w_wLen] = (byte) (w_wRet & 0xFF);
        m_abyPacket2[w_wLen + 1] = (byte) (((w_wRet & 0xFF00) >> 8) & 0xFF);

        return w_wRet;
    }

    boolean checkReceive(short p_wPrefix, short p_wCmd) {
        short w_wCheckSum;
        short w_wTmpPrefix;
        short w_wTmpCmd;
        short w_wLen;

        // Check Prefix Code
        w_wTmpPrefix = (short) (((abyPacket[1] << 8) & 0x0000FF00) | (abyPacket[0] & 0x000000FF));
        w_wTmpCmd = (short) (((abyPacket[3] << 8) & 0x0000FF00) | (abyPacket[2] & 0x000000FF));

        if (w_wTmpCmd != CMD_FP_CANCEL_CODE) {
            if ((p_wPrefix != w_wTmpPrefix) || (p_wCmd != w_wTmpCmd)) {
                return false;
            }
        }

        if (p_wPrefix == RCM_PREFIX_CODE)
            w_wLen = CMD_PACKET_LEN;
        else
            w_wLen = getDataPacketLen();

        w_wCheckSum = (short) (((abyPacket[w_wLen + 1] << 8) & 0x0000FF00) | (abyPacket[w_wLen] & 0x000000FF));

        return w_wCheckSum == getCheckSum(p_wPrefix == RCM_PREFIX_CODE);
    }

    public boolean sendCommand(short p_wCmd) {
        return USB_SendPacket(p_wCmd);
    }

    public boolean USB_SendPacket(short wCMD) {
        LogAndToastUtils.log("Enroll: " + "USB_SendPacket(m_dwCode)");
        byte[] btCDB = new byte[8];
        boolean w_bRet;

        Arrays.fill(btCDB, (byte) 0);
        btCDB[0] = (byte) 0xEF;
        btCDB[1] = 0x11;
        btCDB[4] = CMD_PACKET_LEN + 2;

        while (sendPacketThreadWork) {
            try {
                Thread.currentThread().sleep(3);//毫秒
            } catch (Exception e) {
                LogAndToastUtils.log(e.getMessage());
            }
        }
        sendPacketThreadWork = true;
        w_bRet = usbController.UsbSCSIWrite(btCDB, 8, abyPacket, (int) (CMD_PACKET_LEN + 2), SCSI_TIMEOUT);
        sendPacketThreadWork = false;
        if (!w_bRet)
            return false;
        return USB_ReceiveAck(wCMD);
    }

    public boolean USB_SendPacket2(short wCMD) {
        byte[] btCDB = new byte[8];
        boolean w_bRet;

        Arrays.fill(btCDB, (byte) 0);

        btCDB[0] = (byte) 0xEF;
        btCDB[1] = 0x11;
        btCDB[4] = CMD_PACKET_LEN + 2;

        while (sendPacketThreadWork) {
            try {
                Thread.currentThread().sleep(3);//毫秒
            } catch (Exception e) {
                LogAndToastUtils.log("USB_SendPacket2: " + e.getMessage());
            }
        }
        sendPacketThreadWork = true;
        w_bRet = usbController.UsbSCSIWrite(btCDB, 8, m_abyPacket2, (int) (CMD_PACKET_LEN + 2), SCSI_TIMEOUT);
        sendPacketThreadWork = false;

        if (!w_bRet)
            return false;

        return true;
    }

    public boolean USB_ReceiveAck(short p_wCmd) {
        LogAndToastUtils.log("Enroll: " + "USB_ReceiveAck(wCMD)");
        int w_nLen;
        byte[] btCDB = new byte[8];
        byte[] w_abyWaitPacket = new byte[CMD_PACKET_LEN + 2];
        int w_dwTimeOut = SCSI_TIMEOUT;

        if (p_wCmd == CMD_VERIFY_CODE ||
                p_wCmd == CMD_IDENTIFY_CODE ||
                p_wCmd == CMD_IDENTIFY_FREE_CODE ||
                p_wCmd == CMD_ENROLL_CODE ||
                p_wCmd == CMD_ENROLL_ONETIME_CODE) {
            w_dwTimeOut = (SZ_MAX_FP_TIME_OUT + 1) * (1000);
        }


        Arrays.fill(btCDB, (byte) 0);

        Arrays.fill(w_abyWaitPacket, (byte) 0xAF);
        do {
            Arrays.fill(abyPacket, (byte) 0);

            btCDB[0] = (byte) 0xEF;
            btCDB[1] = (byte) 0x12;

            w_nLen = CMD_PACKET_LEN + 2;

            while (sendPacketThreadWork) {
                try {
                    Thread.currentThread().sleep(3);//毫秒
                } catch (Exception e) {
                    LogAndToastUtils.log(e.getMessage());
                }
            }
            sendPacketThreadWork = true;

            if (!usbController.UsbSCSIRead(btCDB, 8, abyPacket, w_nLen, w_dwTimeOut)) {
                return false;
            }

            sendPacketThreadWork = false;

            try {
                Thread.currentThread().sleep(COMM_SLEEP_TIME);//毫秒
            } catch (Exception ignored) {
            }
            LogAndToastUtils.log("Enroll: memcmp(m_abyPacket, w_abyWaitPacket, CMD_PACKET_LEN + 2)" + memcmp(abyPacket, w_abyWaitPacket, CMD_PACKET_LEN + 2));
        } while (memcmp(abyPacket, w_abyWaitPacket, CMD_PACKET_LEN + 2));

        m_nPacketSize = w_nLen;

        if (!checkReceive((short) RCM_PREFIX_CODE, p_wCmd)) {
            return false;
        }
        return true;
    }

    public boolean USB_ReceiveAck2() {
        int w_nLen;
        byte[] btCDB = new byte[8];
        byte[] w_abyWaitPacket = new byte[CMD_PACKET_LEN + 2];
        int w_dwTimeOut = SCSI_TIMEOUT;

        Arrays.fill(btCDB, (byte) 0);
        Arrays.fill(w_abyWaitPacket, (byte) 0xAF);

        do {
            Arrays.fill(m_abyPacket2, (byte) 0);

            btCDB[0] = (byte) 0xEF;
            btCDB[1] = (byte) 0x12;

            w_nLen = CMD_PACKET_LEN + 2;

            while (sendPacketThreadWork) {
                try {
                    Thread.currentThread().sleep(3);//毫秒
                } catch (Exception e) {
                }
            }
            sendPacketThreadWork = true;

            if (!usbController.UsbSCSIRead(btCDB, 8, m_abyPacket2, w_nLen, w_dwTimeOut))
                return false;

            sendPacketThreadWork = false;

            try {
                Thread.currentThread().sleep(COMM_SLEEP_TIME);//毫秒
            } catch (Exception e) {
            }
        } while (memcmp(m_abyPacket2, w_abyWaitPacket, CMD_PACKET_LEN + 2));

        m_nPacketSize = w_nLen;

        return true;
    }

    @SuppressLint("SuspiciousIndentation")
    boolean USB_ReceiveDataAck(short p_wCmd) {
        LogAndToastUtils.log("USB_ReceiveDataAck(short p_wCmd)");
        byte[] btCDB = new byte[8];
        byte[] w_WaitPacket = new byte[8];
        int w_nLen;
        int w_dwTimeOut = COMM_TIMEOUT;

        if (p_wCmd == CMD_VERIFY_CODE ||
                p_wCmd == CMD_IDENTIFY_CODE ||
                p_wCmd == CMD_IDENTIFY_FREE_CODE ||
                p_wCmd == CMD_ENROLL_CODE ||
                p_wCmd == CMD_ENROLL_ONETIME_CODE)
            w_dwTimeOut = (SZ_MAX_FP_TIME_OUT + 1) * (1000);

        memset(btCDB, (byte) 0, 8);
        memset(w_WaitPacket, (byte) 0xAF, 8);
        Arrays.fill(m_abyPacketTmp, (byte) 0);

        do {
            btCDB[0] = (byte) 0xEF;
            btCDB[1] = 0x15;
            w_nLen = 6;

            if (!usbController.UsbSCSIRead(btCDB, 8, abyPacket, w_nLen, w_dwTimeOut)) {
                return false;
            }
            try {
                Thread.sleep(COMM_SLEEP_TIME);
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }
            LogAndToastUtils.log("do 1");

        } while (memcmp(abyPacket, w_WaitPacket, 6));

        do {
            w_nLen = getDataLen() + 2;
            if (!USB_ReceiveRawData(m_abyPacketTmp, w_nLen)) {
                return false;
            }
            System.arraycopy(m_abyPacketTmp, 0, abyPacket, 6, w_nLen);
            LogAndToastUtils.log("do 2");
            try {
                Thread.currentThread().sleep(COMM_SLEEP_TIME);//毫秒
            } catch (Exception ignored) {
            }
        } while (memcmp(abyPacket, w_WaitPacket, 4));
        if (!checkReceive((short) RCM_DATA_PREFIX_CODE, p_wCmd)) {
            return false;
        }
        return checkReceive((short) RCM_DATA_PREFIX_CODE, p_wCmd);
    }

    boolean USB_SendDataPacket(short wCMD) {
        LogAndToastUtils.log("USB_SendDataPacket");
        byte[] btCDB = new byte[8];
        short w_wLen = (short) (getDataPacketLen() + 2);

        memset(btCDB, (byte) 0, 8);

        btCDB[0] = (byte) 0xEF;
        btCDB[1] = 0x13;

        btCDB[4] = (byte) (w_wLen & 0xFF);
        btCDB[5] = (byte) (((w_wLen & 0xFF00) >> 8) & 0xFF);

        if (!usbController.UsbSCSIWrite(btCDB, 8, abyPacket, getDataPacketLen() + 2, SCSI_TIMEOUT))
            return false;

        return USB_ReceiveDataAck(wCMD);
    }

    public boolean USB_ReceiveDataPacket(short wCMD) {
        return USB_ReceiveDataAck(wCMD);
    }

    boolean USB_ReceiveRawData(byte[] pBuffer, int nDataLen) {
        int w_nDataCnt = nDataLen;
        byte[] btCDB = new byte[8];

        memset(btCDB, (byte) 0, 8);
        btCDB[0] = (byte) 0xEF;
        btCDB[1] = (byte) 0x14;
        return usbController.UsbSCSIRead(btCDB, 8, pBuffer, w_nDataCnt, SCSI_TIMEOUT);
    }


    public boolean memcmp(byte[] p1, byte[] p2, int nLen) {
        int i;

        for (i = 0; i < nLen; i++) {
            if (p1[i] != p2[i])
                return false;
        }

        return true;
    }

    public void memset(byte[] p1, byte nValue, int nLen) {
        Arrays.fill(p1, 0, nLen, nValue);
    }


    public short MAKEWORD(byte low, byte high) {
        short s;
        s = (short) ((((high & 0x00FF) << 8) & 0x0000FF00) | (low & 0x000000FF));
        return s;
    }

    public byte LOBYTE(short s) {
        return (byte) (s & 0xFF);
    }


    public boolean USB_ReceiveImage(byte[] pBuffer, int dwDataLen) {
        byte[] btCDB = new byte[8];
        byte[] w_WaitPacket = new byte[8];
        int w_nI;
        int w_nIndex;
        int w_nRemainCount;
        byte[] w_pTmpImgBuf = new byte[ONCE_UP_IMAGE_UINT];

        memset(btCDB, (byte) 0, 8);
        memset(w_WaitPacket, (byte) 0xAF, 8);

        if (dwDataLen == 208 * 288 || dwDataLen == 242 * 266 || dwDataLen == 202 * 258 || dwDataLen == 256 * 288) {
            w_nIndex = 0;
            w_nRemainCount = dwDataLen;
            w_nI = 0;
            while (w_nRemainCount > ONCE_UP_IMAGE_UINT) {
                btCDB[0] = (byte) 0xEF;
                btCDB[1] = 0x16;
                btCDB[2] = (byte) (w_nI & 0xFF);
                if (!usbController.UsbSCSIRead(btCDB, 8, w_pTmpImgBuf, ONCE_UP_IMAGE_UINT, SCSI_TIMEOUT))
                    return false;
                System.arraycopy(w_pTmpImgBuf, 0, pBuffer, w_nIndex, ONCE_UP_IMAGE_UINT);
                w_nRemainCount -= ONCE_UP_IMAGE_UINT;
                w_nIndex += ONCE_UP_IMAGE_UINT;
                w_nI++;
            }
            btCDB[0] = (byte) 0xEF;
            btCDB[1] = 0x16;
            btCDB[2] = (byte) (w_nI & 0xFF);
            if (!usbController.UsbSCSIRead(btCDB, 8, w_pTmpImgBuf, w_nRemainCount, SCSI_TIMEOUT))
                return false;
            System.arraycopy(w_pTmpImgBuf, 0, pBuffer, w_nIndex, w_nRemainCount);
        }

        return true;
    }
}
